// @ts-check
'use strict';

const fs = require('fs-extra');
//const semver = require('semver');
const path = require('node:path');
const semver = require('semver');
const child_process = require('node:child_process');
//const { URLSearchParams } = require('url');
//let axios;
/*
function rmdirRecursiveSync(path) {
    if (fs.existsSync(path)) {
        fs.readdirSync(path).forEach(file => {
            const curPath = path + '/' + file;
            if (fs.statSync(curPath).isDirectory()) {
                // recurse
                rmdirRecursiveSync(curPath);
            } else {
                // delete file
                fs.unlinkSync(curPath);
            }
        });
        // delete (hopefully) empty folder
        try {
            fs.rmdirSync(path);
        } catch (e) {
            console.log(`Cannot delete directory ${path}: ${e.toString()}`);
        }
    }
}

function findIPs() {
    const ifaces = require('os').networkInterfaces();
    const ipArr = [];
    Object.keys(ifaces).forEach(dev => ifaces[dev].forEach(details => !details.internal && ipArr.push(details.address)));
    return ipArr;
}

function findPath(path, url) {
    if (!url) return '';
    if (url.substring(0, 'http://'.length) === 'http://' ||
        url.substring(0, 'https://'.length) === 'https://') {
        return url;
    } else {
        if (path.substring(0, 'http://'.length) === 'http://' ||
            path.substring(0, 'https://'.length) === 'https://') {
            return (path + url).replace(/\/\//g, '/').replace('http:/', 'http://').replace('https:/', 'https://');
        } else {
            if (url && url[0] === '/') {
                return `${__dirname}/..${url}`;
            } else {
                return `${__dirname}/../${path}${url}`;
            }
        }
    }
}

// Download file to tmp or return file name directly
function getFile(urlOrPath, fileName, callback) {
    axios = axios || require('axios');

    // If object was read
    if (urlOrPath.substring(0, 'http://'.length) === 'http://' ||
        urlOrPath.substring(0, 'https://'.length) === 'https://') {
        const tmpFile = `${__dirname}/../tmp/${fileName || Math.floor(Math.random() * 0xFFFFFFE) + '.zip'}`;
        axios(urlOrPath)
            .then(response => {
                console.log('downloaded ' + tmpFile);
                fs.writeFileSync(tmpFile, response.data);
            })
            .catch(error => {
                console.log(`Cannot download "${tmpFile}": ${error}`);
                callback && callback(tmpFile);
            })
    } else {
        if (fs.existsSync(urlOrPath)) {
            callback && callback(urlOrPath);
        } else if (fs.existsSync(`${__dirname}/../${urlOrPath}`)) {
            callback && callback(`${__dirname}/../${urlOrPath}`);
        } else if (fs.existsSync(`${__dirname}/../tmp/${urlOrPath}`)) {
            callback && callback(`${__dirname}/../tmp/${urlOrPath}`);
        } else if (fs.existsSync(`${__dirname}/../adapter/${urlOrPath}`)) {
            callback && callback(`${__dirname}/../adapter/${urlOrPath}`);
        } else {
            console.log('File not found: ' + urlOrPath);
            process.exit(1);
        }
    }
}

// Return content of the json file. Download it or read directly
function getJson(urlOrPath, callback) {
    axios = axios || require('axios');

    let sources = {};
    // If object was read
    if (urlOrPath && typeof urlOrPath === 'object') {
        callback && callback(urlOrPath);
    } else if (!urlOrPath) {
        console.log('Empty url!');
        callback && callback(null);
    } else {
        if (urlOrPath.substring(0, 'http://'.length) === 'http://' ||
            urlOrPath.substring(0, 'https://'.length) === 'https://') {
            axios(urlOrPath, {timeout: 5000 })
                .then(response => {
                    if (typeof response.data !== 'object') {
                        try {
                            sources = JSON.parse(response.data);
                        } catch (e) {
                            console.log('Json file is invalid on ' + urlOrPath);
                            callback && callback(null, urlOrPath);
                            return;
                        }
                    } else {
                        sources = response.data;
                    }

                    callback && callback(sources, urlOrPath);
                })
                .catch(error => {
                    console.log(`Cannot download json from ${urlOrPath}. Error: ${(error.response && error.response.data) || error.response.status || error}`);
                    callback && callback(null, urlOrPath);
                });
        } else {
            if (fs.existsSync(urlOrPath)) {
                try {
                    sources = JSON.parse(fs.readFileSync(urlOrPath, 'utf8'));
                } catch (e) {
                    console.log(`Cannot parse json file from ${urlOrPath}. Error: ${e}`);
                    callback && callback(null, urlOrPath);
                    return;
                }
                callback && callback(sources, urlOrPath);
            } else if (fs.existsSync(`${__dirname}/../${urlOrPath}`)) {
                try {
                    sources = JSON.parse(fs.readFileSync(`${__dirname}/../${urlOrPath}`, 'utf8'));
                } catch (e) {
                    console.log(`Cannot parse json file from ${__dirname}/../${urlOrPath}. Error: ${e}`);
                    callback && callback(null, urlOrPath);
                    return;
                }
                callback && callback(sources, urlOrPath);
            } else if (fs.existsSync(`${__dirname}/../tmp/${urlOrPath}`)) {
                try {
                    sources = JSON.parse(fs.readFileSync(`${__dirname}/../tmp/${urlOrPath}`, 'utf8'));
                } catch (e) {
                    console.log(`Cannot parse json file from ${__dirname}/../tmp/${urlOrPath}. Error: ${e}`);
                    callback && callback(null, urlOrPath);
                    return;
                }
                callback && callback(sources, urlOrPath);
            } else if (fs.existsSync(`${__dirname}/../adapter/${urlOrPath}`)) {
                try {
                    sources = JSON.parse(fs.readFileSync(`${__dirname}/../adapter/${urlOrPath}`, 'utf8'));
                } catch (e) {
                    console.log(`Cannot parse json file from ${__dirname}/../adapter/${urlOrPath}. Error: ${e}`);
                    callback && callback(null, urlOrPath);
                    return;
                }
                callback && callback(sources, urlOrPath);
            } else {
                //if (urlOrPath.indexOf('/example/') === -1) console.log('Json file not found: ' + urlOrPath);
                callback && callback(null, urlOrPath);
            }
        }
    }
}

// Get list of all installed adapters and controller version on this host
function getInstalledInfo(hostRunningVersion) {
    const result = {};
    let path = __dirname + '/../';
    // Get info about host
    let ioPackage = JSON.parse(fs.readFileSync(path + 'io-package.json', 'utf8'));
    let pack = fs.existsSync(path + 'package.json') ? JSON.parse(fs.readFileSync(path + 'package.json', 'utf8')) : {};
    result[ioPackage.common.name] = {
        controller: true,
        version: ioPackage.common.version,
        icon: ioPackage.common.extIcon || ioPackage.common.icon,
        title: ioPackage.common.title,
        desc: ioPackage.common.desc,
        platform: ioPackage.common.platform,
        keywords: ioPackage.common.keywords,
        readme: ioPackage.common.readme,
        runningVersion: hostRunningVersion,
        license: ioPackage.common.license ? ioPackage.common.license : ((pack.licenses && pack.licenses.length) ? pack.licenses[0].type : ''),
        licenseUrl: (pack.licenses && pack.licenses.length) ? pack.licenses[0].url : ''
    };

    let dirs = fs.readdirSync(__dirname + '/../adapter');

    for (let i = 0; i < dirs.length; i++) {
        try {
            path = `${__dirname}/../adapter/${dirs[i]}/`;
            if (fs.existsSync(path + 'io-package.json')) {
                ioPackage = JSON.parse(fs.readFileSync(path + 'io-package.json', 'utf8'));
                pack = fs.existsSync(path + 'package.json') ? JSON.parse(fs.readFileSync(path + 'package.json', 'utf8')) : {};
                result[ioPackage.common.name] = {
                    controller: false,
                    version: ioPackage.common.version,
                    icon: ioPackage.common.extIcon || (ioPackage.common.icon ? `/adapter/${dirs[i]}/${ioPackage.common.icon}` : ''),
                    title: ioPackage.common.title,
                    desc: ioPackage.common.desc,
                    platform: ioPackage.common.platform,
                    keywords: ioPackage.common.keywords,
                    readme: ioPackage.common.readme,
                    type: ioPackage.common.type,
                    license: ioPackage.common.license ? ioPackage.common.license : ((pack.licenses && pack.licenses.length) ? pack.licenses[0].type : ''),
                    licenseUrl: (pack.licenses && pack.licenses.length) ? pack.licenses[0].url : ''
                };
            }
        } catch (e) {
            console.log(`Cannot read or parse ${__dirname}/../adapter/${dirs[i]}/io-package.json: ${e.toString()}`);
        }
    }
    dirs = fs.readdirSync(__dirname + '/../node_modules');
    for (let i = 0; i < dirs.length; i++) {
        try {
            path = `${__dirname}/../node_modules/${dirs[i]}/`;
            if (dirs[i].match(/^iobroker\./i) && fs.existsSync(path + 'io-package.json')) {
                ioPackage = JSON.parse(fs.readFileSync(path + 'io-package.json', 'utf8'));
                pack = fs.existsSync(path + 'package.json') ? JSON.parse(fs.readFileSync(path + 'package.json', 'utf8')) : {};
                result[ioPackage.common.name] = {
                    controller: false,
                    version: ioPackage.common.version,
                    icon: ioPackage.common.extIcon || (ioPackage.common.icon ? `/adapter/${dirs[i]}/${ioPackage.common.icon}` : ''),
                    title: ioPackage.common.title,
                    desc: ioPackage.common.desc,
                    platform: ioPackage.common.platform,
                    keywords: ioPackage.common.keywords,
                    readme: ioPackage.common.readme,
                    type: ioPackage.common.type,
                    license: ioPackage.common.license ? ioPackage.common.license : ((pack.licenses && pack.licenses.length) ? pack.licenses[0].type : ''),
                    licenseUrl: (pack.licenses && pack.licenses.length) ? pack.licenses[0].url : ''
                };
            }
        } catch (e) {
            console.log(`Cannot read or parse ${__dirname}/../node_modules/${dirs[i]}/io-package.json: ${e.toString()}`);
        }
    }
    if (fs.existsSync(__dirname + '/../../../node_modules/iobroker.js-controller') ||
        fs.existsSync(__dirname + '/../../../node_modules/ioBroker.js-controller')) {
        dirs = fs.readdirSync(__dirname + '/../..');
        for (let i = 0; i < dirs.length; i++) {
            try {
                path = `${__dirname}/../../${dirs[i]}/`;
                if (dirs[i].match(/^iobroker\./i) && dirs[i].substring('iobroker.'.length) !== 'js-controller' &&
                    fs.existsSync(path + 'io-package.json')) {
                    ioPackage = JSON.parse(fs.readFileSync(path + 'io-package.json', 'utf8'));
                    pack = fs.existsSync(path + 'package.json') ? JSON.parse(fs.readFileSync(path + 'package.json', 'utf8')) : {};
                    result[ioPackage.common.name] = {
                        controller: false,
                        version: ioPackage.common.version,
                        icon: ioPackage.common.extIcon || (ioPackage.common.icon ? `/adapter/${dirs[i]}/${ioPackage.common.icon}` : ''),
                        title: ioPackage.common.title,
                        desc: ioPackage.common.desc,
                        platform: ioPackage.common.platform,
                        keywords: ioPackage.common.keywords,
                        readme: ioPackage.common.readme,
                        license: ioPackage.common.license ? ioPackage.common.license : ((pack.licenses && pack.licenses.length) ? pack.licenses[0].type : ''),
                        licenseUrl: (pack.licenses && pack.licenses.length) ? pack.licenses[0].url : ''
                    };
                }
            } catch (e) {
                console.log(`Cannot read or parse ${__dirname}/../node_modules/${dirs[i]}/io-package.json: ${e.toString()}`);
            }
        }
    }
    return result;
}
*/

/**
 * Reads an adapter's npm version
 * @param {string | null} adapter The adapter to read the npm version from. Null for the root ioBroker packet
 * @param {(err: Error | null, version?: string) => void} [callback]
 */
/*
function getNpmVersion(adapter, callback) {
    adapter = adapter ? 'iobroker.' + adapter : 'iobroker';
    adapter = adapter.toLowerCase();

    const cliCommand = `npm view ${adapter}@latest version`;

    const exec = require('child_process').exec;
    exec(cliCommand, { timeout: 2000 }, (error, stdout, stderr) => {
        let version;
        if (error) {
            // command failed
            if (typeof callback === 'function') {
                callback(error);
                return;
            }
        } else if (stdout) {
            version = semver.valid(stdout.trim());
        }

        typeof callback === 'function' && callback(null, version);
    });
}

function getIoPack(sources, name, callback) {
    getJson(sources[name].meta, function (ioPack) {
        const packUrl = sources[name].meta.replace('io-package.json', 'package.json');

        getJson(packUrl, pack => {
            // If installed from git or something else
            // js-controller is exception, because can be installed from npm and from git
            if (sources[name].url && name !== 'js-controller') {
                if (ioPack && ioPack.common) {
                    sources[name] = Object.assign(sources[name], ioPack.common);
                    if (pack && pack.licenses && pack.licenses.length) {
                        sources[name].license = sources[name].license || pack.licenses[0].type;
                        sources[name].licenseUrl = sources[name].licenseUrl || pack.licenses[0].url;
                    }
                }

                callback && callback(sources, name);
            } else {
                if (ioPack && ioPack.common) {
                    sources[name] = Object.assign(sources[name], ioPack.common);
                    if (pack && pack.licenses && pack.licenses.length) {
                        sources[name].license = sources[name].license || pack.licenses[0].type;
                        sources[name].licenseUrl = sources[name].licenseUrl || pack.licenses[0].url;
                    }
                }

                if (sources[name].meta.substring(0, 'http://'.length) === 'http://' ||
                    sources[name].meta.substring(0, 'https://'.length) === 'https://') {
                    //installed from npm
                    getNpmVersion(name, function (err, version) {
                        if (version) {
                            sources[name].version = version;
                        }
                        callback && callback(sources, name);
                    });
                } else {
                    callback && callback(sources, name);
                }
            }
        });
    });
}

// Get list of all adapters and controller in some repository file or in /conf/source-dist.json
function getRepositoryFile(urlOrPath, callback) {
    let sources = {};
    let path = '';
    let toRead = 0;
    let timeout = null;
    let count = 0;

    if (urlOrPath) {
        const parts = urlOrPath.split('/');
        path = parts.splice(0, parts.length - 1).join('/') + '/';
    }

    // If object was read
    if (urlOrPath && typeof urlOrPath === 'object') {
        callback && callback(urlOrPath);
    } else if (!urlOrPath) {
        try {
            sources = JSON.parse(fs.readFileSync(__dirname + '/../conf/sources.json', 'utf8'));
        } catch (e) {
            sources = {};
        }
        try {
            const sourcesDist = JSON.parse(fs.readFileSync(__dirname + '/../conf/sources-dist.json', 'utf8'));
            sources = Object.assign({}, sourcesDist, sources);
        } catch (e) {
            // Don't care
        }

        for (const name in sources) {
            if (sources[name].url) {
                sources[name].url = findPath(path, sources[name].url);
            }
            if (sources[name].meta) {
                sources[name].meta = findPath(path, sources[name].meta);
            }
            if (sources[name].icon) {
                sources[name].icon = findPath(path, sources[name].icon);
            }

            if (!sources[name].version && sources[name].meta) {
                toRead++;
                count++;
                getIoPack(sources, name, (ignore, name) => {
                    toRead--;
                    if (!toRead && timeout) {
                        clearTimeout(timeout);
                        callback && callback(sources);
                        timeout = null;
                        callback = null;
                    }
                });
            }
        }

        if (!toRead) {
            callback && callback(sources);
        } else {
            timeout = setTimeout(() => {
                if (timeout) {
                    console.log(`Timeout by read all package.json (${count}) seconds`);
                    clearTimeout(timeout);
                    callback && callback(sources);
                    timeout = null;
                    callback = null;
                }
            }, count * 500);
        }
    } else {
        getJson(urlOrPath, function (sources) {
            if (sources) {
                for (const name in sources) {
                    if (!sources.hasOwnProperty(name)) {
                        continue;
                    }
                    if (sources[name].url) {
                        sources[name].url = findPath(path, sources[name].url);
                    }
                    if (sources[name].meta) {
                        sources[name].meta = findPath(path, sources[name].meta);
                    }
                    if (sources[name].icon) {
                        sources[name].icon = findPath(path, sources[name].icon);
                    }

                    if (!sources[name].version && sources[name].meta) {
                        toRead++;
                        count++;
                        getIoPack(sources, name, (ignore, name) => {
                            toRead--;
                            if (!toRead && timeout) {
                                clearTimeout(timeout);
                                callback && callback(sources);
                                timeout = null;
                                callback = null;
                            }
                        });
                    }
                }
            }
            if (!toRead) {
                callback && callback(sources);
            } else {
                timeout = setTimeout(() => {
                    if (timeout) {
                        console.log(`Timeout by read all package.json (${count}) seconds`);
                        clearTimeout(timeout);
                        callback && callback(sources);
                        timeout = null;
                        callback = null;
                    }
                }, count * 500);
            }
        });
    }
}

function sendDiagInfo(obj, callback) {
    axios = axios || require('axios');

    const objStr = JSON.stringify(obj);
    const params = new URLSearchParams();
    params.append('data', objStr);

    const config = {
        headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
        timeout: 4000
    };

    axios.post(`http://download.${appName}.net/diag.php`, params, config)
        .catch(error => console.warn(`Cannot send diag info: ${error.message}`))
        .then(() => callback && callback());
}

function getAdapterDir(adapter, isNpm) {
    const parts = __dirname.replace(/\\/g, '/').split('/');
    parts.splice(parts.length - 3, 3);
    let dir = parts.join('/');
    if (adapter.startsWith('iobroker.')) {
        adapter = adapter.substring('iobroker.'.length);
    }

    if (fs.existsSync(dir + '/node_modules/iobroker.js-controller') &&
        fs.existsSync(`${dir}/node_modules/iobroker.${adapter}`)) {
        dir = __dirname.replace(/\\/g, '/').split('/');
        dir.splice(dir.length - 2, 2);
        return `${dir.join('/')}/iobroker.${adapter}`;
    } else if (fs.existsSync(`${__dirname}/../node_modules/iobroker.${adapter}`)) {
        dir = __dirname.replace(/\\/g, '/').split('/');
        dir.splice(dir.length - 1, 1);
        return `${dir.join('/')}/node_modules/iobroker.${adapter}`;
    } else if (fs.existsSync(`${__dirname}/../adapter/${adapter}`)) {
        dir = __dirname.replace(/\\/g, '/').split('/');
        dir.splice(dir.length - 1, 1);
        return `${dir.join('/')}/adapter/${adapter}`;
    } else {
        if (isNpm) {
            if (fs.existsSync(__dirname + '/../../node_modules/iobroker.js-controller')) {
                dir = __dirname.replace(/\\/g, '/').split('/');
                dir.splice(dir.length - 2, 2);
                return `${dir.join('/')}/iobroker.${adapter}`;
            } else {
                dir = __dirname.replace(/\\/g, '/').split('/');
                dir.splice(dir.length - 1, 1);
                return `${dir.join('/')}/node_modules/iobroker.${adapter}`;
            }
        } else {
            dir = __dirname.replace(/\\/g, '/').split('/');
            dir.splice(dir.length - 1, 1);
            return `${dir.join('/')}/adapter/${adapter}`;
        }
    }
}
*/
// All paths are returned always relative to /node_modules/iobroker.js-controller
function getDefaultDataDir() {
    /** @type {string | string[]} */
    // let dataDir = __dirname.replace(/\\/g, '/');
    // dataDir = dataDir.split('/');

    // If installed with npm
    if (fs.existsSync(`${__dirname}/../../../node_modules/iobroker.js-controller`)) {
        return '../../iobroker-data/';
    }
    // dataDir.splice(dataDir.length - 1, 1);
    // dataDir = dataDir.join('/');
    return './data/';
}

function getConfigFileName() {
    /** @type {string | string[]} */
    let configDir = __dirname.replace(/\\/g, '/');
    configDir = configDir.split('/');

    // If installed with npm
    if (fs.existsSync(`${__dirname}/../../../node_modules/iobroker.js-controller`) ||
        fs.existsSync(`${__dirname}/../../../node_modules/ioBroker.js-controller`)) {
        // remove /node_modules/ioBroker.js-controller/lib
        configDir.splice(configDir.length - 3, 3);
        configDir = configDir.join('/');
        return `${configDir}/iobroker-data/iobroker.json`;
    }

    // Remove /lib
    configDir.splice(configDir.length - 1, 1);
    configDir = configDir.join('/');
    if (fs.existsSync(`${__dirname}/../conf/iobroker.json`)) {
        return `${configDir}/conf/iobroker.json`;
    }
    return `${configDir}/data/iobroker.json`;
}

/**
 * Tests if we are currently inside a node_modules folder
 * @returns {boolean}
 */
/*
function isThisInsideNodeModules() {
    return /[\\/]node_modules[\\/]/.test(__dirname) || /[\\/]node_modules[\\/]/.test(process.cwd());
}
*/
/**
 * Recursively enumerates all files in the given directory
 * @param {string} dir The directory to scan
 * @param {(name: string) => boolean} [predicate] An optional predicate to apply to every found file system entry
 * @returns {string[]} A list of all files found
 */
function enumFilesRecursiveSync(dir, predicate) {
    const ret = [];
    if (typeof predicate !== 'function') {
        predicate = () => true;
    }
    // enumerate all files in this directory
    const filesOrDirs = fs.readdirSync(dir)
        .filter(predicate) // exclude all files starting with "."
        .map(f => path.join(dir, f)) // and prepend the full path
        ;
    for (const entry of filesOrDirs) {
        if (fs.statSync(entry).isDirectory()) {
            // Continue recursing this directory and remember the files there
            Array.prototype.push.apply(ret, enumFilesRecursiveSync(entry, predicate));
        } else {
            // remember this file
            ret.push(entry);
        }
    }
    return ret;
}

/**
 * Recursively copies all files from the source to the target directory
 * @param {string} sourceDir The directory to scan
 * @param {string} targetDir The directory to copy to
 * @param {(name: string) => boolean} [predicate] An optional predicate to apply to every found file system entry
 */
function copyFilesRecursiveSync(sourceDir, targetDir, predicate) {
    // Enumerate all files in this module that are supposed to be in the root directory
    const filesToCopy = enumFilesRecursiveSync(sourceDir, predicate);
    // Copy all of them to the corresponding target dir
    for (const file of filesToCopy) {
        // Find out where it's supposed to be
        const targetFileName = path.join(targetDir, path.relative(sourceDir, file));
        // Ensure the directory exists
        fs.ensureDirSync(path.dirname(targetFileName));
        // And copy the file
        fs.copySync(file, targetFileName);
    }
}

/** Checks if this installation process is automated */
function isAutomatedInstallation() {
    return !!process.env.AUTOMATED_INSTALLER;
}

/**
 * Retrieves the version of the globally installed npm and node
 * @returns {{npm: string, node: string}}
 */
function getSystemVersions() {
    // Run npm -v and extract the version string
    const ret = {
        npm: undefined,
        node: undefined,
    };
    try {
        let npmVersion;
        ret.node = semver.valid(process.version);
        try {
            // remove local node_modules\.bin dir from a path
            // or we potentially get a wrong npm version
            const newEnv = Object.assign({}, process.env);
            newEnv.PATH = (newEnv.PATH || newEnv.Path || newEnv.path)
                .split(path.delimiter)
                .filter(dir => {
                    dir = dir.toLowerCase();
                    return !dir.includes('iobroker') || !dir.includes(path.join('node_modules', '.bin'));
                })
                .join(path.delimiter);

            npmVersion = child_process.execSync('npm -v', { encoding: 'utf8', env: newEnv });
            if (npmVersion) npmVersion = semver.valid(npmVersion.trim());
            console.log(`NPM version: ${npmVersion}`);
            ret.npm = npmVersion;
        } catch (e) {
            console.error(`Error trying to check npm version: ${e.message}`);
        }
    } catch (e) {
        console.error(`Could not check npm version: ${e.message}`);
        console.error('Assuming that correct version is installed.');
    }
    return ret;
}

module.exports = {
    /*findIPs,
    rmdirRecursiveSync,
    getRepositoryFile,
    getFile,
    getJson,
    getInstalledInfo,
    sendDiagInfo,
    getAdapterDir,
    isThisInsideNodeModules,
    enumFilesRecursiveSync,*/
    getDefaultDataDir,
    getConfigFileName,
    copyFilesRecursiveSync,
    isAutomatedInstallation,
    getSystemVersions,
};
